package gr.aueb.cs.mscis.chordroid.app;

import gr.aueb.cs.mscis.chordroid.ChordroidService;
import gr.aueb.cs.mscis.chordroid.chord.NodeIdentifier;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import android.util.Log;

/**
 * Main purpose of this class is the instantiation
 * of the FileSeeder and the FileLeecher classes for
 * uploading and downloading files from the system respectively.
 * A map is also being used for collecting the responses from
 * the Chord layer that originated from FindSuccesor calls
 * @author lupin
 *
 */
public class FileApp implements Runnable {
	
	private FileSeeder seeder;
	private FileLeecher leecher;
	private int port = 5000;
	// Pair of Hash and Address + port of the owner
	private ConcurrentMap<String,NodeIdentifier> hashMap = new ConcurrentHashMap<String, NodeIdentifier>();
	
	public FileApp() { }

	public byte[] searchFile(String filename) {
		// kalese leecher
		leecher = new FileLeecher(this, filename);
		return leecher.requestFile();
	}
	
	/**
	 * Uploads a file with the specified filename and data
	 * @param filename
	 * @param data
	 */
	public void uploadFile(String filename, byte[] data) { // change signature if you want to specify replicas and/or chunk size
		// kalese seeder
		seeder = new FileSeeder(this, filename, data);
		seeder.uploadFile();
		seeder = null; // let gb have it
		Log.v("FileApp", "upload done!");
	}

	/**
	 * The first String is the filename hash
	 * The second String is the successor addr
	 * @return
	 */
	public ConcurrentMap<String,NodeIdentifier> getHashMap() {
		return hashMap;
	}

	/** 
	 * Returns if available the {@code NodeIdentifier} for a hash
	 * @param filenameHash
	 * @return
	 */
	public NodeIdentifier getResponseByHash(String filenameHash) {
		if (getHashMap().containsKey(filenameHash)) return getHashMap().get(filenameHash);
		return null;
	}
	
	/**
	 * Returns a Set of the {@code NodeIdentifier}s that have responded to a
	 * findSuccessor call
	 * @return
	 */
	public Set<NodeIdentifier> getNodes() {
		Set<NodeIdentifier> nodes = new HashSet<NodeIdentifier>();
		for (Entry<String, NodeIdentifier> entry : getHashMap().entrySet()) {
			if (!nodes.contains(entry.getValue())) nodes.add(entry.getValue()); 
		}
		return nodes;
	}
	
	/**
	 * Returns a Set of the keys that correspond to specific {@code NodeIdentifier}
	 * @param node
	 * @return
	 */
	public Set<String> getKeysByNode(NodeIdentifier node) {
	     Set<String> keys = new HashSet<String>();
	     for (Entry<String, NodeIdentifier> entry : getHashMap().entrySet()) {
	         if (entry.getValue().equals(node)) {
	             keys.add(entry.getKey());
	         }
	     }
	     return keys;
	}

	/**
	 * Set the port
	 * @param port
	 */
	public void setPort(int port) {
		this.port = port;
	}

	/**
	 * Gets the App port
	 * @return
	 */
	public int getPort() {
		return port;
	}
	
	/**
	 * Returns the Upload Progress -more precisely: the progress of the multiple
	 * FindSuccessor calls
	 * @return
	 */
	public float getUploadProgress() {
		return (seeder != null) ? seeder.getFindSuccessorProgress() : 0;
	}

	public void run() {
		try {
			ServerSocket welcomeSocket = new ServerSocket(port);
			for (;;) {
				Socket inFromAnotherApp = welcomeSocket.accept();
				ObjectInputStream ois = new ObjectInputStream(inFromAnotherApp.getInputStream());
				Object obj = ois.readObject();
								
				if (obj instanceof String) {
					// that's a hash - return the chunk
					Log.v("app","String req");
					String hash = (String) obj;
					FileChunk chunk = ChordroidService.getDht().getByHash(hash);
					if (chunk == null) inFromAnotherApp.close();
					else {ObjectOutputStream oos = new ObjectOutputStream(inFromAnotherApp.getOutputStream());
					oos.writeObject(chunk);
					}
				} 
				else if (obj instanceof FileChunk) {
					// that's a chunk - save it
					FileChunk chunk = (FileChunk) obj;
					ChordroidService.getDht().addEntry(chunk.getHash(), chunk);
				}
				else if (obj instanceof Set<?>) {
					// that's a bunch of chunks -save them
					Set<FileChunk> packet = (Set<FileChunk>) obj; 
					for(FileChunk chunk : packet) {
						if (chunk != null) ChordroidService.getDht().addEntry(chunk.getHash(), chunk);
					}
				}
				if (inFromAnotherApp != null) inFromAnotherApp.close();
			}
			
			} catch (IOException e) {
				e.printStackTrace();
				Log.v("FileApp",e.getMessage());
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
	}

	/**
	 * returns the seeder
	 * @return
	 */
	public FileSeeder getSeeder() {
		return seeder;
	}

	/**
	 * returns the leecher
	 * @return
	 */
	public FileLeecher getLeecher() {
		return leecher;
	}
}
